﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DistributedGraph.FloodSet
{
    /// <summary>
    /// Algorithmencontroller für FloodSet
    /// </summary>
    public class AlgorithmController : IAlgorithmController<Node>
    {
        #region Attribute
        private static Random random = new Random();

        private int round;
        private int process;
        private List<Node> nodes;
        private AlgorithmInput algorithmInput;
        private int fixedF = -1;
        /// <summary>
        /// Gibt die maximal tollerierte Fehlerzahl an.
        /// </summary>
        private int f
        {
            get
            {
                if (fixedF >= 0) return fixedF; // fester Wert
                int failingNodes = 0;
                foreach (Node n in nodes) failingNodes += n.FailStops ? 1 : 0;
                return Math.Max(failingNodes, 1);    // dynamischer Wert
            }
        }
        #endregion Attribute

        #region Eigenschaften
        /// <summary>
        /// Konstante für den Namen
        /// </summary>
        public const string NAME = "FloodSet";

        /// <summary>
        /// Gibt den Namen an.
        /// </summary>
        public string Name { get { return NAME; } }

        /// <summary>
        /// Gibt die aktuelle Runde an.
        /// </summary>
        public int Round { get { return round; } }

        /// <summary>
        /// Gibt die maximale Rundenanzahl an.
        /// </summary>
        public int MaxRounds { get { return f + 1; } }

        /// <summary>
        /// Gibt den aktuellen Prozess an.
        /// </summary>
        public int CurrentProcess { get { return process; } }

        /// <summary>
        /// Gibt die Liste der Knoten zurück.
        /// </summary>
        public List<Node> Nodes { get { return nodes; } }

        /// <summary>
        /// Event das gerufen wird, wenn sich der Status geändert hat.
        /// </summary>
        public event SimpleAlgorithmEvent<Node> StateChanged;

        /// <summary>
        /// Event das gerufen wird, wenn eine Runde beendet wurde.
        /// </summary>
        public event ProgressAlgorithmEvent RoundFinished;

        /// <summary>
        /// Event das gerufen wird, sobald ein Prozess abläuft.
        /// </summary>
        public event ProgressAlgorithmEvent ProcessStarted;
        #endregion Eigenschaften

        #region Methoden
        /// <summary>
        /// Instanziiert und initialisiert die Klasse.
        /// </summary>
        /// <param name="nodeCount">Wieviele Knoten sich im Graphen befinden sollen.</param>
        /// <param name="algorithmInput">Gibt den Wertebereich für die Knoten an.</param>
        public AlgorithmController(int nodeCount, AlgorithmInput algorithmInput)
        {
            if (nodeCount < 2) throw new ArgumentException("Der Wert muss mindestens 2 sein.", "nodeCount");

            this.algorithmInput = algorithmInput;
            this.round = 0;
            this.process = 0;

            // Graphen erstellen
            int singleValue = random.Next(1, nodeCount);
            this.nodes = Node.CreateNtoNGraph<Node>(nodeCount);
            foreach (Node n in nodes) n.Value = algorithmInput == AlgorithmInput.MultiValue ? random.Next(1, nodeCount) : singleValue;
        }

        /// <summary>
        /// Instanziiert und initialisiert die Klasse.
        /// </summary>
        /// <param name="nodeCount">Wieviele Knoten sich im Graphen befinden sollen.</param>
        /// <param name="algorithmInput">Gibt den Wertebereich für die Knoten an.</param>
        /// <param name="maxF">Die maximale Anzahl der ausgefallenen Knoten.</param>
        public AlgorithmController(int nodeCount, AlgorithmInput algorithmInput, int maxF)
            : this(nodeCount, algorithmInput)
        {
            if (maxF < 0) throw new ArgumentException("Der Wert muss mindestens 0 sein.", "maxF");
            if (maxF >= nodeCount) throw new ArgumentException("Der Wert kleiner als 'nodeCount' sein.", "maxF");
            this.fixedF = maxF;
        }

        /// <summary>
        /// Fährt mit dem nächsten Prozess fort.
        /// </summary>
        public void NextProcess()
        {
            if (Round + 1 > MaxRounds) return;
            if (ProcessStarted != null) ProcessStarted(Math.Max(round - (process == 0 ? 1 : 0), 0), round, process);
            nodes[process].Broadcast(new CleanMessage());
            process = (process + 1) % nodes.Count;
            round += process == 0 ? 1 : 0;
            int f = this.f; // Für Laufzeit da sonst immer wieder ermittelt werden müsste.
            if (process == 0)
            {
                foreach (Node n in nodes) n.FinishRound(f);
                if (RoundFinished != null) RoundFinished(round - 1, round, 0);
            }
            if (StateChanged != null) StateChanged(this);
        }

        /// <summary>
        /// Fährt fort bis die aktuelle Runde beendet ist.
        /// </summary>
        public void FinishRound()
        {
            if (Round + 1 > MaxRounds) return;
            do { NextProcess(); } while (process != 0);
        }

        /// <summary>
        /// Schließt den Algorithmus ab.
        /// </summary>
        public void Finish()
        {
            while (Round + 1 <= MaxRounds) FinishRound();
        }
        #endregion Methoden
    }
}
